

#include "cybertron/core/Log.hpp"
#include "cybertron/core/UtilCRT.hpp"
#include "cybertron/core/UtilPath.hpp"
#include "cybertron/core/UtilString.hpp"
#include <google/protobuf/message.h>
#include "../PlatformHeader.hpp"

#if CYBERTRON_WIN
#pragma warning(disable:4091)
#include <imagehlp.h>
#pragma comment(lib, "imagehlp.lib")
#endif

CYBERTRON_BEGIN

CybertronCoreAPI void UtilCRT::initialize()
{
#ifdef _MSC_VER
	/*
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	int breakOnAllocateId = 0;
	if (breakOnAllocateId > 0)
	{
		_CrtSetBreakAlloc(breakOnAllocateId);
	}
	*/
#endif
}

CybertronCoreAPI void UtilCRT::shutdown()
{
	google::protobuf::ShutdownProtobufLibrary();
}

#if CYBERTRON_WIN

void _INNER_WriteCrashType(EXCEPTION_POINTERS* pExcept)
{
	EXCEPTION_RECORD* pRecord = pExcept->ExceptionRecord;
	CONTEXT* pContext = pExcept->ContextRecord;

	// get the module name
	logError("Exception Code: %d", pRecord->ExceptionCode);

	switch (pRecord->ExceptionCode)
	{
	case EXCEPTION_ACCESS_VIOLATION:
	{
		logError("(ACCESS_VIOLATION)");
		if (2 == pRecord->NumberParameters)
		{
			const char* accessType = "read";
			if (pRecord->ExceptionInformation[0] != 0)
			{
				accessType = "write";
			}

			logError("Memory at 0x%08X could not be %s", pRecord->ExceptionInformation[1], accessType);
		}
	}
	break;
	case EXCEPTION_INT_DIVIDE_BY_ZERO:
	{
		logError("(DIVIDE_BY_ZERO)");
	}
	break;
	case EXCEPTION_STACK_OVERFLOW:
	{
		logError("(STACK_OVERFLOW)");
	}
	break;
	default:
	break;
	}

	return;
}

BOOL __stdcall _INNER_ReadProcessMemory(
	HANDLE hProcess,
	DWORD64 qwBaseAddress,
	PVOID lpBuffer,
	DWORD nSize,
	LPDWORD lpNumberOfBytesRead)
{
	SIZE_T st = 0;
	BOOL bRet = ReadProcessMemory(hProcess, (LPVOID)qwBaseAddress, lpBuffer, nSize, &st);

	if (NULL != lpNumberOfBytesRead)
	{
		*lpNumberOfBytesRead = (DWORD)st;
	}
	return bRet;
}

LONG WINAPI SystemExceptionHandler(struct _EXCEPTION_POINTERS* pException)
{
	EXCEPTION_POINTERS* pExcept = (EXCEPTION_POINTERS*)(pException);
	CONTEXT* pContext = pExcept->ContextRecord;
	HANDLE hProcess = ::GetCurrentProcess();
	HANDLE hThread = ::GetCurrentThread();

	if (pContext == nullptr)
	{
		return EXCEPTION_EXECUTE_HANDLER;
	}

	::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
	std::string symbolPath = UtilPath::getExecutablePath();
	BOOL bHasSymbol = ::SymInitialize(hProcess, &symbolPath[0], TRUE);

	IMAGEHLP_LINE64 line;
	char symbol[sizeof(IMAGEHLP_SYMBOL64) + 256];
	IMAGEHLP_SYMBOL64 * pSymbol = (IMAGEHLP_SYMBOL64 *)symbol;

	CONTEXT& l_context = *pContext;

	DWORD l_imageType;
	STACKFRAME64 l_stackFrame;
	memset(&l_stackFrame, 0, sizeof(l_stackFrame));
#ifdef _M_IX86
	// normally, call ImageNtHeader() and use machine info from PE header
	l_imageType = IMAGE_FILE_MACHINE_I386;
	l_stackFrame.AddrPC.Offset = l_context.Eip;
	l_stackFrame.AddrPC.Mode = AddrModeFlat;
	l_stackFrame.AddrFrame.Offset = l_context.Ebp;
	l_stackFrame.AddrFrame.Mode = AddrModeFlat;
	l_stackFrame.AddrStack.Offset = l_context.Esp;
	l_stackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
	l_imageType = IMAGE_FILE_MACHINE_AMD64;
	l_stackFrame.AddrPC.Offset = l_context.Rip;
	l_stackFrame.AddrPC.Mode = AddrModeFlat;
	l_stackFrame.AddrFrame.Offset = l_context.Rbp;
	l_stackFrame.AddrFrame.Mode = AddrModeFlat;
	l_stackFrame.AddrStack.Offset = l_context.Rsp;
	l_stackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
	l_imageType = IMAGE_FILE_MACHINE_IA64;
	l_stackFrame.AddrPC.Offset = l_context.StIIP;
	l_stackFrame.AddrPC.Mode = AddrModeFlat;
	l_stackFrame.AddrFrame.Offset = l_context.IntSp;
	l_stackFrame.AddrFrame.Mode = AddrModeFlat;
	l_stackFrame.AddrBStore.Offset = l_context.RsBSP;
	l_stackFrame.AddrBStore.Mode = AddrModeFlat;
	l_stackFrame.AddrStack.Offset = l_context.IntSp;
	l_stackFrame.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif

	_INNER_WriteCrashType(pExcept);

	logError("Callstack:");

	while (::StackWalk64(
		l_imageType, 
		hProcess, 
		hThread,
		&l_stackFrame,
		&l_context,
		_INNER_ReadProcessMemory,
		SymFunctionTableAccess64, 
		SymGetModuleBase64, 
		NULL))
	{
		if (bHasSymbol)
		{
			::memset(pSymbol, 0, sizeof(symbol));
			pSymbol->SizeOfStruct = sizeof(symbol);
			pSymbol->MaxNameLength = 256;
			DWORD64 displacement64 = 0;
			DWORD displacement = 0;

			if (::SymGetSymFromAddr64(hProcess, l_stackFrame.AddrPC.Offset, &displacement64, pSymbol))
			{
				logError("%s()", pSymbol->Name);
				::memset(&line, 0, sizeof(line));
				line.SizeOfStruct = sizeof(line);
				displacement = 0;

				if (::SymGetLineFromAddr64(hProcess, l_stackFrame.AddrPC.Offset, &displacement, &line))
				{
					logError("%s, %d", line.FileName, line.LineNumber);
				}
			}
		}
	}

	if (bHasSymbol)
	{
		::SymCleanup(hProcess);
	}

	return EXCEPTION_EXECUTE_HANDLER;
}

#endif

void callWithCppExceptionHandling(
	const char* functionName, 
	std::function<void()>* func)
{
	try
	{
		(*func)();
	}
	catch (std::exception& e)
	{
		logError("Function %s: Exception: %s", functionName, e.what());
		return;
	}
	catch (...)
	{
		logError("Function %s: Exception Unknown!", functionName);
		return;
	}
}

void callWithSehExceptionHandling(
	const char* functionName, 
	std::function<void()>* func)
{
#if CYBERTRON_WIN
	__try
	{
		callWithCppExceptionHandling(functionName, func);
	}
	__except (SystemExceptionHandler(GetExceptionInformation()))
	{
		logError("Function %s: Exception SEH!", functionName);
	}
#else
	callWithCppExceptionHandling(functionName, func);
#endif
}

CybertronCoreAPI void UtilCRT::call(
	const char* functionName, 
	std::function<void()> func)
{
	if (functionName == nullptr)
	{
		functionName = "Unknown";
	}

	callWithSehExceptionHandling(functionName, &func);
}

CybertronCoreAPI std::shared_ptr<std::thread> UtilCRT::startThread(
	const char* threadName,
	std::function<void()> threadProc)
{
	logSilent("Thread %s Started.", threadName);

	std::string nameClone = threadName;

	auto pThread = std::make_shared<std::thread>([nameClone, threadProc]
	{
		UtilCRT::call(nameClone.c_str(), [threadProc]() { threadProc();  });

		logSilent("Thread %s Terminated.", nameClone.c_str());
	});

	return pThread;
}

CYBERTRON_END
